﻿using System;
using System.Data;
using MySql.Data.MySqlClient;
using MySql.Data.Types;
using System.Threading;
using snat.EventFramework.Errors;
using snat.loggers;
using proxy.exceptions;

namespace snat.proxy {
    /// <summary>
    /// A connector to the sql database
    /// </summary>
    /// <remarks>
    /// This connector is a singleton class and therefore cannot be instanciated
    /// </remarks>
    public sealed class DataProxy {

        private static volatile DataProxy instance = new DataProxy();
        private MySqlConnection[] connections;
        private object[] connectionLocks;
        private Semaphore connectionSem;

        private DataProxy() {
            int connectionCount = 2;
            connections = new MySqlConnection[connectionCount];
            connectionLocks = new object[connectionCount];
            connectionSem = new Semaphore(connectionCount, connectionCount);
        }

        /// <summary>
        /// Get the instance in the program of DataProxy
        /// </summary>
        public static DataProxy Instance {
            get {
                return instance;
            }
        }

        /// <summary>
        /// Finalization method for the DataProxy
        /// </summary>
        /// <remarks>
        /// The proxyLock and queryLocks are locked in case a slow query is still in the tubes.
        /// Shouldn't happen, but safety first.
        /// </remarks>
        ~DataProxy() {
            //ensure all connections not active and can not be used
            for (int finished = 0; finished < connections.Length; finished++) connectionSem.WaitOne();
            //close all connections
            foreach (MySqlConnection connection in connections) connection.Close();
        }



        public void Connect(string serverURI, int port, string databaseName, string userName, string password) {
            int failiures = 0;
            for (int i = 0; i < connections.Length; i++) {
                connectionLocks[i] = new object();
                try {
                    connections[i] = new MySqlConnection(
                        String.Join(String.Empty,
                                    "server=", serverURI,
                                    ";port=", port,
                                    ";database=", databaseName,
                                    ";uid=", userName,
                                    ";password=", password,
                                    ";Persist Security Info=False;"));
                    connections[i].Open();
                } catch (MySqlException ex) {
                    failiures++;

                    if (failiures == connections.Length) {
                        throw new DataProxyException(ex.Message, ex);
                    }
                }
            }
        }

        public DataSet RunQuery(MySqlCommand sqlQuery, int position, int bufferSize) {
            connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                MySqlCommand command = sqlQuery.Clone();

                //if a timeout has occurred retry opening
                if (connections[i].State != ConnectionState.Open){
                    connections[i].Close();
                    connections[i].Open();     
                 }
                command.Connection = connections[i];

                command.Parameters.AddWithValue("position", position);
                command.Parameters.AddWithValue("bufferSize", bufferSize);
                command.Prepare();

                adapter.SelectCommand = command;
                adapter.Fill(dataSet);
            } catch (MySqlConversionException ex) {
                   throw ex;
            }


            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);


            connectionSem.Release();
            return dataSet;

        }

        [Obsolete("Not sure why it was ever created")]
        public DataSet RunQuery(MySqlCommand sqlQuery) {
            connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                MySqlCommand command = sqlQuery.Clone();
                command.Connection = connections[i];
                adapter.SelectCommand = command;
                adapter.Fill(dataSet);
            } catch (MySqlConversionException ex) {
                //Error(ex.Source, new ErrorArgs("MySqlConversion Exception in Dataproxy", ex.Message + "\nThe query ran was:\n\t" + sqlQuery, severity.Major, ex));
                throw ex;
            }


            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);


            connectionSem.Release();
            return dataSet;


        }

        public DataSet RunQuery(string sqlQuery) {
            connectionSem.WaitOne(); //ensure never more threads than connectors
            //todo check if connected and reconnect
            DataSet dataSet = new DataSet();

            int i = 0;

            while (!Monitor.TryEnter(connectionLocks[i])) {
                i += 1;
                i %= connectionLocks.Length; //should never wrap round, but defensive code
            }

            try {
                MySqlDataAdapter adapter = new MySqlDataAdapter();
                adapter.SelectCommand = new MySqlCommand(sqlQuery, connections[i]);
                adapter.Fill(dataSet);
            } catch (MySqlConversionException ex) {
               // Error(ex.Source, new ErrorArgs("MySqlConversion Exception in Dataproxy", ex.Message + "\nThe query ran was:\n\t" + sqlQuery, severity.Major, ex));
                throw ex;
            }
            //no need to pulse, never more than one waiting
            Monitor.Exit(connectionLocks[i]);
            connectionSem.Release();
            return dataSet;
        }




    }
}
